package com.young.cloud.gateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.young.common.client.ums.UmsClientContext;
import com.young.common.util.*;
import com.young.interfaces.ums.model.SsoVerifyDTO;
import com.young.interfaces.ums.model.User;
import com.young.interfaces.ums.service.IUmsOutService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Base64;

/**
 * 单点登录过滤器
 * Created by Administrator on 2019/12/28 0028.
 */
@Component
public class SsoFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(SsoFilter.class);
    /**
     * 配置参数
     */
    @Autowired
    SsoConfiguration ssoConfiguration;
    /**
     * ums服务
     */
    @Autowired
    IUmsOutService umsOutService;

    /*
     * filterType：返回一个字符串代表过滤器的类型，在zuul中定义了四种不同生命周期的过滤器类型，具体如下：
     * pre：路由之前
     * routing：路由之时
     * post： 路由之后
     * error：发送错误调用
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /*
     * filterOrder：过滤的顺序
     */
    @Override
    public int filterOrder() {
        return 1;
    }

    /*
     * shouldFilter：这里可以写逻辑判断，是否要过滤
     */
    @Override
    public boolean shouldFilter() {
        return ssoConfiguration.isEnable();
    }

    /*
     * run：过滤器的具体逻辑。可以很复杂，包括查sql，nosql去判断该请求到底有没有权限访问，
     * 这里是判断请求有没有 token 参数
     */
    @Override
    public Object run() throws ZuulException {
        //请求上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        //请求
        HttpServletRequest request = ctx.getRequest();
        //相应
        HttpServletResponse response = ctx.getResponse();

        Boolean sso = false;

        //sso验证
        try {
            sso = this.sso(request, response, ctx);
        } catch (IOException e) {
            logger.error("[SSO过滤器] sso验证发生异常,默认验证失败", e);
        }

        if (!sso) {
            logger.debug("[SSO过滤器] sso验证失败,终止请求继续转发,url={}", request.getRequestURL());
            //终止请求的转发
            ctx.setSendZuulResponse(false);
        }

        return null;
    }

    /**
     * 判断url是否需要登录
     * 白名单url或者重定向url不需要登录即可访问
     * @param curUri 请求地址,servletPath
     * @return
     */
    private boolean isNeedLoginUrl(String curUri){
        PathMatcher matcher = new AntPathMatcher();
        if (ssoConfiguration.getSsoWhite() != null) {
            for(String url : ssoConfiguration.getSsoWhite()){
                if(matcher.match(url, curUri)){//匹配
                    logger.debug("[SSO过滤器] ServletPath[{}]属于白名单[{}]", curUri, url);
                    return false;
                }
            }
        }
        if (curUri.equals(ssoConfiguration.getSsoRedirectUrl())){
            logger.debug("[SSO过滤器] ServletPath[{}]属于白名单[{}]", curUri, ssoConfiguration.getSsoRedirectUrl());
            return false;
        }
        return true;
    }

    /**
     * 校验
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    private boolean sso(HttpServletRequest request, HttpServletResponse response, RequestContext ctx) throws IOException {
        logger.debug("[SSO过滤器] 当前请求URL={}", request.getRequestURL());
        //设置用户上下文
        User user = checkToken(request);
        //当单点验证开关开启且用户信息无效并且当前请求需要登录时,对请求做拦截处理
        if (user == null && this.isNeedLoginUrl(request.getServletPath())){
            logger.info("[SSO过滤器] 当前请求登录用户信息无效,需重新登录,IP={},URL={}", ClientInfoUtil.getClientIpAddr(request), request.getRequestURL());
            if (CommonUtil.isAjaxRequest(request)){//ajax请求
                //写入失败结果
                CommonUtil.write2Response(response, Result.build(Result.CodeEnum.SESSION_TIMEOUT));
            } else {
                if (StringUtils.isNotBlank(ssoConfiguration.getSsoRedirectUrl())) {
                    //请求的url
                    String url = request.getRequestURL().toString();
                    response.sendRedirect(ssoConfiguration.getSsoRedirectUrl() + (ssoConfiguration.getSsoRedirectUrl().indexOf("?") > 0 ? "&" : "?") + "fromUrl=" + URLEncoder.encode(url, "UTF-8"));
                } else {
                    logger.warn("[SSO过滤器] ssoRedirectUrl参数无效,请检查young.cloud.gateway.sso.enable.ssoRedirectUrl配置是否正常");
                }
            }
            return false;
        }

        //权限校验, 开关打开且登陆用户有效时验证
        if (ssoConfiguration.isAuthCheck() && user != null) {
            String uri = request.getRequestURI();
            if (umsOutService.canAccessUri(user.getId(), uri)) {
                logger.debug("[SSO过滤器] 当前请求uri={},用户id={},可以访问该uri", uri, user.getId());//debug
            } else {
                logger.info("[SSO过滤器] 当前请求uri={},用户id={},无权限访问", uri, user.getId());
                //写入失败结果
                CommonUtil.write2Response(response, Result.build(Result.CodeEnum.NOT_AUTH));
                return false;
            }

        }

        //设置用户到上下文
        //UmsClientContext.setLoginUser(user);
        //用户信息传递到下游
        if (user != null) {
            ctx.addZuulRequestHeader(CommonConstants.HEADER_GATEWAY_USER_INFO, URLEncoder.encode(JsonUtil.toJson(user), "UTF-8"));
        }

        return true;
    }

    /**
     * token验证
     * @param request
     * @return
     */
    private User checkToken(HttpServletRequest request) {
        String token = request.getParameter("_token");
        Cookie cookie = CookieUtil.getCookie(request, "_token");//从cookie中取令牌
        if (cookie != null && StringUtils.isNotBlank(cookie.getValue())){//cookie存在令牌
            if (StringUtils.isNotBlank(token)){//存在入参token
                if (!token.equals(cookie.getValue())){//cookie和传入的不一致,报错
                    logger.warn("[SSO过滤器] cookie与请求体不一致,cookie={},请求体={}", cookie.getValue(), token);
                    return null;
                }
            }
            token = cookie.getValue();
        }
        //从header中取
        if (StringUtils.isBlank(token)) {
            token = request.getHeader("_token");
        }
        if (StringUtils.isBlank(token)){//cookie/入参/header都为空
            logger.debug("[SSO过滤器] token令牌为空:cookie/请求体/header都没有token信息,URL={}", request.getRequestURL());
            return null;
        }

        try {
            logger.debug("[SSO过滤器] 当前会话token={}", token);
            //验证token有效性并返回用户信息(若有效的话)
            SsoVerifyDTO ssoVerifyDTO = umsOutService.verifySSO(token);
            if (ssoVerifyDTO != null && ssoVerifyDTO.isSuccess()){
                logger.debug("[SSO过滤器] 当前登录用户信息打印:{}", ssoVerifyDTO.getUser());
                return ssoVerifyDTO.getUser();
            }

        } catch (Exception e) {
            logger.error("[SSO过滤器] token验证发生异常,token={}", token, e);
        }
        return null;
    }
}
